package com.googlecode.lucene.appengine;

import java.io.IOException;
import java.util.List;

import org.apache.lucene.store.Lock;
import org.apache.lucene.store.LockFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.lucene.appengine.objectify.util.ObjectifyUtil;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;

/**
 * Class used as {@link LockFactory} for any {@link GaeDirectory}.
 * 
 * @author Fabio Grucci (github: <i>UltimaPhoenix</i>, bitbucket: <i>Dark_Phoenix</i>, googlecode: <i>fabio.grucci</i>)
 *
 */
final class GaeLockFactory extends LockFactory {
	
	private static final Logger log = LoggerFactory.getLogger(GaeLockFactory.class);
	
	private final ObjectifyFactory objectifyFactory;
	
	private final Key<LuceneIndex> indexKey;
	
	private GaeLockFactory(Key<LuceneIndex> indexKey, ObjectifyFactory objectifyFactory) {
		this.indexKey = indexKey;
		this.objectifyFactory = objectifyFactory;
	}
	/**
	 * Return a new {@link GaeLockFactory} specific for the index.
	 * @param indexKey
	 * @return A {@link GaeLockFactory} for this index
	 */
	public static GaeLockFactory getInstance(Key<LuceneIndex> indexKey) {
		return new GaeLockFactory(indexKey, GaeObjectifyFactory.getInstance());
	}
	/**
	 * Method that return every lock created by this {@link GaeLockFactory}.
	 * @return The list of {@link GaeLock} created by this {@link GaeLockFactory}
	 */
	List<GaeLock> getLocks() {
		return objectifyFactory.begin().query(GaeLock.class).ancestor(indexKey).list();
	}
	/*
	 * (non-Javadoc)
	 * @see org.apache.lucene.store.LockFactory#makeLock(java.lang.String)
	 */
	@Override
	public Lock makeLock(final String lockName) {
		return new Lock() {
			/*
			 * (non-Javadoc)
			 * @see org.apache.lucene.store.Lock#release()
			 */
			@Override
			public void release() throws IOException {
				final Objectify objectify = objectifyFactory.beginTransaction();
				try {
					final GaeLock gaeLock = objectify.find(new Key<GaeLock>(indexKey, GaeLock.class, lockName));
					if(gaeLock != null && gaeLock.locked) {
						log.debug("Unlocking Lock '{}'.", lockName);
						gaeLock.locked = false;
						objectify.put(gaeLock);
					} else {
						log.warn("Tryng to release a non locked Lock '{}'.", lockName);
					}
					ObjectifyUtil.commit(objectify);
				} finally {
					ObjectifyUtil.closeQuietly(objectify);
				}
			}
			/*
			 * (non-Javadoc)
			 * @see org.apache.lucene.store.Lock#obtain()
			 */
			@Override
			public boolean obtain() throws IOException {
				final boolean obtained;
				
				final Objectify objectify = objectifyFactory.beginTransaction();
				try {
					GaeLock gaeLock = objectify.find(new Key<GaeLock>(indexKey, GaeLock.class, lockName));
					if(gaeLock == null) {
						log.trace("Creating new Lock '{}.{}'.", indexKey, lockName);
						gaeLock = new GaeLock(indexKey, lockName);
					}
					if(gaeLock.locked) {
						log.debug("Cannot lock Lock '{}.{}'.", indexKey, lockName);
						obtained = false;
					} else {
						log.debug("Locking Lock '{}.{}'.", indexKey, lockName);
						gaeLock.locked = true;
						objectify.put(gaeLock);
						obtained = true;
					}
					ObjectifyUtil.commit(objectify);
					return obtained;
				} finally {
					ObjectifyUtil.closeQuietly(objectify);
				}
			}
			/*
			 * (non-Javadoc)
			 * @see org.apache.lucene.store.Lock#isLocked()
			 */
			@Override
			public boolean isLocked() throws IOException {	
				final GaeLock find = objectifyFactory.begin().find(new Key<GaeLock>(indexKey, GaeLock.class, lockName));
				return find != null && find.locked;
			}
		};
	}
	/*
	 * (non-Javadoc)
	 * @see org.apache.lucene.store.LockFactory#clearLock(java.lang.String)
	 */
	@Override
	public void clearLock(String lockName) throws IOException {
		objectifyFactory.begin().delete(GaeLock.class, lockName);
	}
	
}